package org.openspml.providers.one_c;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.openspml.message.SpmlRequest;
import org.openspml.message.SpmlResponse;
import org.openspml.providers.one_c.api.connection.GlobalContext;
import org.openspml.server.SpmlHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.jacob.com.ComThread;

/**
 * Base handler for 1C requests.
 * 
 * @author <A href="mailto:abashev at gmail dot com">Alexey Abashev</A>
 * @version $Id$
 */
public class OneCSpmlHandler implements SpmlHandler {
	private static final String COMMAND_CLASS_PREFIX = "org.openspml.providers.one_c.commands.";
	private static final String COMMAND_CLASS_SUFIX = "Command";
	private static final String COMMAND_METHOD = "execute";
	
	private final Logger log = LoggerFactory.getLogger(OneCSpmlHandler.class);
	
	private Map<String, TypedSpmlHandler> typedHandlers = new ConcurrentHashMap<String, TypedSpmlHandler>();
	
	private GlobalContextFactory contextFactory = new GlobalContextFactory();
	
    @Override
	public SpmlResponse doRequest(SpmlRequest request) {
    	final Class<? extends SpmlRequest> requestClass = request.getClass();
		final String className = requestClass.getName();

    	if (log.isTraceEnabled()) {
    		log.trace("Got request - {}", request);
    	}
    	
    	if (typedHandlers.containsKey(className)) {
    		// We found already processed command
    		TypedSpmlHandler h = typedHandlers.get(className);

    		// Making fast invoke
    		SpmlResponse response;

    		try {
    			response = h.doRequest(request);
    		} catch (IllegalArgumentException e) {
    			log.error("Something bad happens. This exception must be filtered before.", e);

    			response = fail(request, e);
    		} catch (IllegalAccessException e) {
    			log.error("Something bad happens. This exception must be filtered before.", e);

    			response = fail(request, e);
    		} catch (InvocationTargetException e) {
    			response = fail(request, e);
    		}

    		return response;
    	}
    	
    	// Trying to find command for processing
    	log.debug("Trying to find command for processing {}", request);
    	
    	int l = requestClass.getPackage().getName().length();
    	final String shortName = className.substring(l + 1);
		final String commandClass = COMMAND_CLASS_PREFIX + shortName + COMMAND_CLASS_SUFIX;

		TypedSpmlHandler handler = null;
		SpmlResponse response = fail(request, SpmlResponse.ERROR_UNSUPPORTED_OPERATION);
    	boolean error = false; // System error
    	
    	// Looking for command class 
    	try {
			Class<? extends Object>	clazz = Class.forName(commandClass);
			
			handler = new ReflectionSpmlHandler(clazz, requestClass);
			response = handler.doRequest(request);
		} catch (ClassNotFoundException e) {
			error = true;

			log.warn("Command for processing {} not found", request);
		} catch (SecurityException e) {
			error = true;

			log.warn("Security violation for accessing command [{}]", commandClass);
		} catch (InstantiationException e) {
			error = true;
			
			log.warn("Unable to create command class [{}]", commandClass);
		} catch (IllegalAccessException e) {
			error = true;

			log.warn("Illegal access for command class ["  + commandClass + "]", e);
		} catch (NoSuchMethodException e) {
			error = true;
			
			log.warn("Unable to find processing method [{}] in command [{}]", COMMAND_METHOD, commandClass);
		} catch (IllegalArgumentException e) {
			error = true;
			
			log.warn("Wrong argument type in method [{}] in command [{}]", COMMAND_METHOD, commandClass);
		} catch (InvocationTargetException e) {
			log.error("Exception in [" + request + "] request processing", e.getCause());
			
			response = fail(request, e.getCause());
		}

		typedHandlers.put(className, (error) ? new DummySpmlHandler() : handler);

		return response;
    }

    private SpmlResponse fail(SpmlRequest request, String error) {
        SpmlResponse response = request.createResponse();

		response.setRequestId(getOrCreateRequestId(request));
        response.setError(error);
        response.setErrorMessage("error message");
        
        return response;
    }

    private SpmlResponse fail(SpmlRequest request, Throwable throwable) {
        SpmlResponse response = request.createResponse();

		response.setRequestId(getOrCreateRequestId(request));
        response.setError(SpmlResponse.ERROR_CUSTOM_ERROR);
        response.setErrorMessage(throwable);
        
        return response;
    }

    private String getOrCreateRequestId(SpmlRequest request) {
        String rid = request.getRequestId();
        
        if (rid == null) {
            rid = "rid" + System.currentTimeMillis() + System.identityHashCode(request);
        }
        
        request.setRequestId(rid);
        
        return rid;
    }

    private interface TypedSpmlHandler {
		SpmlResponse doRequest(
				SpmlRequest request
		) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;
    }
    
    /**
     * This class making ivoke for specific command, converting results and so on.
     * 
     * @author <A href="mailto:abashev at gmail dot com">Alexey Abashev</A>
     * @version $Id$
     */
    private class ReflectionSpmlHandler implements TypedSpmlHandler {
		private Object handler;
		private Method method;
		
		public ReflectionSpmlHandler(
				Class<? extends Object> handlerClass, 
				Class<? extends SpmlRequest> requestClass
		) throws InstantiationException, IllegalAccessException, SecurityException, NoSuchMethodException {
			handler = handlerClass.newInstance();
			method = handlerClass.getDeclaredMethod(COMMAND_METHOD, GlobalContext.class, requestClass);

			if (method == null) {
				throw new NoSuchMethodException();
			}
		}
		
		public SpmlResponse doRequest(
				SpmlRequest request
		) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
			SpmlResponse response;
			GlobalContext context = null;
			
			ComThread.InitMTA();
			
			try {
				context = contextFactory.getContext();
				response = (SpmlResponse) method.invoke(handler, context, request);
			} finally {
				if (context != null) {
					contextFactory.recycleContext(context);
				}
				
				ComThread.Release();
			}
			
			if (response != null) {
				response.setRequestId(getOrCreateRequestId(request));
			}

			return response;
		}
    }
    
    private class DummySpmlHandler implements TypedSpmlHandler {
		@Override
		public SpmlResponse doRequest(
				SpmlRequest request
		) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
			return fail(request, SpmlResponse.ERROR_UNSUPPORTED_OPERATION);
		}
    }
}